import socket
import json
import datetime
import time
import hashlib
from threading import *

from myLog import logger


class LuxClient():
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.skt_client = None
        self.thread_end_flag = False

        self.event_L001 = Event() # 线程阻塞标识符，通过标识符判断服务端是否做出应答
        self.event_L002 = Event() 
        self.event_L003 = Event()
        self.event_L004 = Event() 

        self.L002_dict = {}


    def connectToServer(self):
        logger.info("准备连接服务端...")
        self.skt_client = socket.socket()
        server_addr = (self.ip, self.port)
        self.skt_client.settimeout(5)

        i = 0
        while True:
            try:
                i += 1
                self.skt_client.connect(server_addr)
            except Exception as e:
                if e.args[0] == 'timed out':
                    time.sleep(5)                   # 连接失败则10秒后重连
                    logger.warning("第 {} 次连接失败.{}".format(i, e))
                    continue
                else:
                    time.sleep(10)                  # 连接失败则10秒后重连
                    logger.warning("第 {} 次连接失败.{}".format(i, e))
                    continue
            else:
                logger.info("连接成功 √ ")
                return True

    # 发送心跳请求 L001 (子线程t1)
    def heartBeat(self):
        i = 0
        while True:
            if self.thread_end_flag:
                break
            headFlag = 'DU'
            funcCode = "L001"
            tid = str(int(time.time()*1000))
            dataBody = {"status":"0",
                        "Manufacturer":"NVT"
                        }
            send_result = self.sendToServer(headFlag, funcCode, tid, dataBody)
            send_result = 'Success'
            time.sleep(1)

            if send_result == 'Fail':           # send失败，说明连接出现了问题
                i += 1
                if i > 3:
                    logger.warning("heartBeat has failed three times in a row. Ready to reconnect...")
                    # time.sleep(1)
                    # skt_client.close()       # 先关掉旧的socket
                    self.skt_client = self.connectToServer()
                    logger.info("Reconnect successfully")
                    continue
            if send_result == 'Success':
                self.event_L001.wait(timeout = 5)    # 等待recv()接收消息成功的反馈
                if self.event_L001.is_set():
                    self.event_L001.clear()          # event_S401重新置为False
                    i = 0
                    time.sleep(1)
                else:
                    i += 1
                    logger.warning("heartBeat response timeout for the {} time".format(i))
                    if i > 3:
                        logger.warning("heartBeat response timeout for the {} time. Ready to reconnect...")
                        # time.sleep(1)
                        self.skt_client.close()   # 先关掉旧的socket
                        self.skt_client = self.connectToServer()
                        logger.warning("Reconnect successfully")
                        continue


    def calculate_crc(self, data):
        """计算CRC校验值"""
        crc = hashlib.md5(data.encode()).hexdigest()[:4]
        return crc


# 给服务端发消息
    def sendToServer(self, HeadFlag, FuncCode, TID, DataBody):
        time_now = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[2:17]
        DataLen = len(json.dumps(DataBody).replace(' ','')) # int类型，如78
        DataLen = "%04d" % DataLen                          # 格式化成4字符str，如'0078'
        
        request_data = {"headFlag":HeadFlag,
                "version":"V1.0",
                "funcCode":FuncCode,
                "tid":TID,
                "dataLen":DataLen,
                "dataBody":DataBody,
                }
        request_json = json.dumps(request_data)
        CRC = "A1B2"
        # ---------发数据---------
        str_json = "Lux " + str(len(request_json)).zfill(4) + " " + request_json + " " + CRC + "\r\n"
        try:
            self.skt_client.send(str_json.encode())
        except Exception as e:
            logger.error("sendToServer() 失败: {} -- {}".format(FuncCode, e))
            return 'Fail'      # send失败通常是IOError，是因为服务端连接断开了
        else:
            # logger.info("sendToServer() 成功: {}".format(FuncCode))
            return 'Success'   # 只表示发送成功了，对方是否应答成功需要再判断


    # 接收客户端消息  (子线程t2)
    def recvFromServer(self):
        while True:
            if self.thread_end_flag:
                break
            try:
                data = self.skt_client.recv(1024).decode('utf-8')
                # logger.info('recvFromServer() success: {}'.format(data))
            except Exception as e:
                if e.args[0] == 'timed out':
                    logger.warning("recvFromServer() failed: {}".format(e))
                    continue
                else:
                    # logger.warning("recv() failed: {}".format(e))
                    # time.sleep(0.5)
                    continue  # 断联了，继续接收消息，直到重连成功
            else:
                if data:
                    self.analyze_data(data)
                # else:
                #     logger.warning("The received data is empty.")


    # 对接收的数据进行解析
    def analyze_data(self, data):
        global Event_TID
        data_list = data.split("\r\n")               # 用换行符切分消息字符串，生成list
        for s in data_list:
            if s == "":
                data_list.remove(s)                  # 去除列表里面的空字符串,留下有效数据

        for data_unique in data_list:
            try:
                # logger.info("data_unique:{}".format(data_unique))
                data_unique = data_unique[9:-5]
                data_dict = json.loads(data_unique)  # json_str转dict
            except Exception as e:
                logger.error("解析错误：".format(e))
            else:
                funcCode_recv = data_dict.get("funcCode")
                headFlag_recv = data_dict.get("headFlag")
                tid_recv = data_dict.get("tid")
                dataBody_rev = data_dict.get("dataBody")
                # logger.warning("----data----: {}-{}".format(funcCode_recv, headFlag_recv))

                # 把数据解析出来后，根据内容进行处理。可能收到以下三类消息:

                # ---- 收到服务端的心跳应答
                if (funcCode_recv == "L001") & (headFlag_recv == "DUR"):
                    # logger.info("收到心跳请求")
                    # L001_DUR_dict[funcCode_recv] = dataBody_rev
                    self.event_L001.set()

                # --- 服务端下载秘钥的请求
                elif (funcCode_recv == "L002") & (headFlag_recv == "TU"):
                    print("收到了服务端下载秘钥的请求: {}".format(funcCode_recv))
                    self.L002_dict["tid"] = tid_recv
                    self.event_L002.set()

                # # --- 服务端下载进度的应答
                elif (funcCode_recv == "L003") & (headFlag_recv == "DUR"):
                    # S403_DUR_dict[FuncCode_recv] = data_dict
                    self.event_L003.set()

                # # --- 服务端下载结果的应答
                elif (funcCode_recv == "L004") & (headFlag_recv == "DUR"):
                    # S404_DUR_dict[FuncCode_recv] = data_dict
                    self.event_L004.set()


    # 处理服务端下载秘钥的请求 (子线程t3)
    def downloadKeyResponse(self):
        while True:
            # logger.info("等待服务端下载秘钥的请求... self.event_L002.is_set(): {}".format(self.event_L002.is_set()))

            # self.event_L002.wait(timeout = 5)
            if self.event_L002.is_set():
                # print("收到了服务端下载秘钥的请求: {}".format(funcCode))
                self.event_L002.clear()

                headFlag = "TUR"
                funcCode = "L002"
                tid = self.L002_dict["tid"]
                dataBody = {"resCode": "0"}
                if self.sendToServer(headFlag, funcCode, tid, dataBody) != 'Success':
                    pass
                    logger.error("应答服务端下载秘钥的请求 失败: {}".format(funcCode))
                else:
                    pass
                    logger.info("应答服务端下载秘钥的请求 成功: {}".format(funcCode))
                    time.sleep(0.2)
                    self.reportDownloadProgress(50)
                    time.sleep(0.5)
                    self.reportDownloadProgress(80)
                    time.sleep(0.5)
                    self.reportDownloadProgress(100)
                    time.sleep(0.5)
                    result = 1 # 成功
                    self.reportDownloadResult(result)

                    
            else:
                continue



    # 上报下载进度 下载秘钥 L003 DU
    def reportDownloadProgress(self, progress_value):
        for loop in range(3):
            headFlag = "DU"
            funcCode = "L003"
            tid = str(int(time.time()*1000))
            dataBody = {"progress" : str(progress_value)}

            if self.sendToServer(headFlag, funcCode, tid, dataBody) != 'Success':
                logger.error("上报下载进度失败: {}".format(funcCode))
                continue

            self.event_L003.wait(timeout = 5)
            if self.event_L003.is_set():
                logger.info("上报下载进度成功: {}".format(funcCode))
                self.event_L003.clear()
                break

    # 上报下载结果 L004 DU
    def reportDownloadResult(self, result):
        for loop in range(3):
            headFlag = "DU"
            funcCode = "L004"
            tid = str(int(time.time()*1000))
            dataBody = {"result" : str(result)}

            if self.sendToServer(headFlag, funcCode, tid, dataBody) != 'Success':
                logger.error("上报下载结果失败: {}".format(funcCode))
                continue

            self.event_L004.wait(timeout = 5)
            if self.event_L004.is_set():
                logger.info("上报下载结果成功: {}".format(funcCode))
                self.event_L004.clear()
                break



def main():
    ip = input("请输入IP: ")
    port = input("请输入端口号:")

    myclient = LuxClient(str(ip), int(port))
    # 连接服务端
    if myclient.connectToServer() != False:
        # 启动心跳请求 子线程
        time.sleep(1)
        t1 = Thread(target = myclient.heartBeat)
        logger.info("启动心跳请求子线程")
        t1.start()

        # 启动 接受消息 的子线程
        t2 = Thread(target = myclient.recvFromServer)
        logger.info("启动接受消息子线程")
        t2.start()

        # 处理下载秘钥请求 的子线程
        t3 = Thread(target = myclient.downloadKeyResponse)
        logger.info("启动处理下载秘钥请求子线程")
        t3.start()


if __name__ == "__main__":
    main()
















